package core;

import enums.ResponseErrorCode;
import exception.ServerException;
import request.Request;
import response.ErrorResponse;
import response.Response;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * 一个Handler即为一条线程，处理单个连接
 * <dl>
 *     <dt>Handler工作流程</dt>
 *     <dd>1. 接收客户端发来的一个Request</dd>
 *     <dd>2. Request会交给指定Controller处理</dd>
 *     <dd>3.1. Controller处理完后返回对应Response</dd>
 *     <dd>3.2. Controller处理时出现异常返回ErrorResponse</dd>
 *     <dd>4. 将Response发给客户端</dd>
 * </dl>
 */
public class Handler implements Runnable {
    private final Socket socket;
    private ObjectInputStream objectInputStream;
    private ObjectOutputStream objectOutputStream;
    public static final ThreadLocal<InetAddress> addressThreadLocal = new ThreadLocal<>();
    public static final ThreadLocal<Integer> portThreadLocal = new ThreadLocal<>();

    public Handler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        addressThreadLocal.set(socket.getInetAddress());
        portThreadLocal.set(socket.getPort());
        try {
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());

            // 1. receive client's request
            Request request = (Request) objectInputStream.readObject();

            // 2. let RequestMappingAdapter handle request, and accept handle method's response
            Response response = RequestMappingAdapter.handle(request);

            // 3. send response to the client
            objectOutputStream.writeObject(response);
            objectOutputStream.flush();
        } catch (ServerException e) { // exception from RequestMappingAdapter.handle
            // global exception handle
            handleError(e.getErrorCode());
        } catch (Exception e) { // other exception (maybe IOException)
            throw new RuntimeException(e);
        } finally {
            addressThreadLocal.remove();
            portThreadLocal.remove();
            // calling shutdownInput and shutdownOutput before close, for gently ending socket stream
            try {
                socket.shutdownInput();
            } catch (IOException ignored) {
            }
            try {
                socket.shutdownOutput();
            } catch (IOException ignored) {
            }
            try {
                socket.close();
            } catch (IOException ignored) {
            }
        }
    }

    private void handleError(ResponseErrorCode errorCode) {
        ErrorResponse response = new ErrorResponse(errorCode);
        try {
            objectOutputStream.writeObject(response);
            objectOutputStream.flush();
        } catch (Exception ignored) {
        }
    }
}
